package fatboy.hypeerweb;

import java.util.ArrayList;
import java.util.List;

import fatboy.addons.Broadcast;
import fatboy.addons.FinalListener;
import fatboy.addons.Broadcast2;
import fatboy.addons.Listener;
import fatboy.addons.PrintOut;
import fatboy.addons.SendMessage;
import fatboy.communication.Command;
import fatboy.communication.ObjectID;
import fatboy.communication.RemoteLocation;
import fatboy.communication.ResidentDatabase;
import fatboy.communication.Server;
//import fatboy.communication.Server;

import junit.framework.Assert;
/**
 * A class used to keep a connection into a hypeerweb while only needing
 * the pointer to one Node
 * @author Jack
 * @domain domain of Node for this connection X domain of possible hypeerwebs X Power Set of 
 * possible Nodes - sets that aren't sorted(i.e. smallest Node coming first) for list of Nodes
 */
public class Connection {
	private static Node myConnection;
	private List<Node> allNodes;
	private int nodeCount;
	private Server listener;
	
	/**
	 * Constructor to connect into a hypeerweb that already exists 
	 * @pre connection is connected to a tree, or this method will start a
	 * new tree with it
	 * @post returns a connection into the hypeerweb
	 */
	public Connection(Node connection){
		//preseting the first node
		setMyConnection(connection);
		setAllNodes(new ArrayList<Node>());
		allNodes.add(connection);
		nodeCount = 1;
		ResidentDatabase.initialize();
		listen();
	}
	/**
	 * constructor that takes a computer name and port number and connects this 
	 * hypeerweb into a hypeerweb that is open on that machine
	 * @param comp
	 * @param portNumber
	 * @pre the other machine has a hypeerweb up and running on that port
	 * @post this hypeer web is linked into the remote hypeerweb
	 */
	public Connection(String comp, String portNumber){
		ResidentDatabase.initialize();
		RemoteLocation seed = new RemoteLocation(comp,portNumber);
		setMyConnection(this.getAccessPoint(seed));
		nodeCount = 0;
		setAllNodes(new ArrayList<Node>());
		listen();
	}
	/**
	 * basic Constructor to start an empty hypeerweb
	 */
	public Connection(){
		setMyConnection(null);
		setAllNodes(new ArrayList<Node>());
		nodeCount = 0;
		ResidentDatabase.initialize();
		listen();
	}
	
	/**
	 * makes a Node in that hypeerweb which contains the Object o
	 * @pre o is not null
	 * @post the hypeerweb now contains a node that contains o and returns said node
	 */
	public Node addNode(Object o){
		
		if(o == null){
			throw new IllegalArgumentException("Cannot handle null data");
		}
		
		if(getMyConnection() == null){
			setMyConnection(new Node(o,NodeType.LAST_NODE,new NodeAddress(0)));
			//getAllNodes().add(getMyConnection());
			nodeCount++;
			 
			ResidentDatabase.setObject(getMyConnection().getId(), getMyConnection());
			getAllNodes().add(getMyConnection());
			return getMyConnection();		
		}
		
		Node ret = new Node(o,new NodeAddress(Node.BOGUS));
		ResidentDatabase.setObject(ret.getId(), ret);
		getMyConnection().add(ret);
		getAllNodes().add(ret);
		//put an object in the object database
		
		nodeCount ++;
		return ret;
		
	}
	public void clean(){
		List<Node> removers = new ArrayList<Node>();
		for(Node temp : allNodes){
			if(temp.getAddress().getAddress() ==  Node.BOGUS){
				removers.add(temp);
			}
		}
		for(Node bad : removers){
			allNodes.remove(bad);
		}
	}
	/**
	 * Removes a node with a specific Node Address from the hypeerweb
	 * 
	 * @param node - reference to the node to be removed
	 * @pre address is valid and Node with this address exists in the hypeerweb
	 * @post hypeerweb has 1 node less, it itself valid and all the pointers set correctly
	 */	
	public Node removeNode(Node node) {
		if(node.getAddress().getAddress()==0){
			return null;
		}
//		Assert.assertTrue(allNodes.contains(node));
		//allNodes.remove(node);
		
		if(getMyConnection().equals(node)){
			setMyConnection(getMyConnection().remove(node));
		}
		else{
			getMyConnection().remove(node);
		}
		clean();
		nodeCount--;
		//ResidentDatabase.remove(node.getId());
//		Assert.assertEquals(allNodes.size(), nodeCount);
		return node;
	}	
	
	/**
	 * Sends the message from the root of the hypeerweb (node with address 0)
	 *  to the target node
	 * @param target Target Node, the destination of the message
	 * @param message Visitor implementation
	 * @pre Target node should be a valid member of the hypeeweb formed at
	 * 	this connection, message should be an implementation of the
	 *  Visitor interface
	 * @post Message sent from the root of the hypeerweb to the target
	 * 	node, and executed on the target node
	 */
	public void sendMessage(Node target, Object message){
		SendMessage msg = new SendMessage();
		msg.sendMessage(myConnection,target, message);
		
	}
	
	/**
	 * Sends the message  from the origin node to the target node of the hypeerweb
	 * @param origin Sender node, the origin of the message
	 * @param target Target node, the destination of the message
	 * @param message Visitor implementation
	 * @pre Origin and Target nodes should be valid members of the same hypeeweb
	 *  formed at this connection, message should be an implementation of the
	 *  Visitor interface
	 * @post Message sent from the origin node to the target node  of the
	 *  hypeerweb, and executed on the target node
	 */
	public void sendMessage(Node origin, Node target, Object message){
		SendMessage msg = new SendMessage();
		msg.sendMessage(origin, target, message);
		
	}
	public void sendMessage(Node origin, Node target){
		SendMessage msg = new SendMessage();
		msg.sendMessage(origin, target, new PrintOut());
		
	}
	/**
	 * 
	 * @param origin
	 * @return sum of broadcast total
	 */
	public int broadcast(Node origin) {

		/*if(!allNodes.contains(origin)){
			throw new IllegalArgumentException("Node does not exist");
		}*/
		Broadcast broadcaster = new Broadcast();
		origin.accept(broadcaster, origin);
		System.out.println("Broadcast from: "+ origin + " complete");
		return broadcaster.getBroadcastTotal();
	}
	
	/**
	 * 
	 * @param origin
	 * @return sum total of all visited nodes in broadcast
	 */
	public void broadcastWithAck(Node origin) {
//		System.out.println(origin.getAddress());

		/*if(!allNodes.contains(origin)){
			throw new IllegalArgumentException("Node does not exist");
		}*/
		Listener printer = new FinalListener(1,origin);
		Broadcast2 broadcaster = new Broadcast2(printer);
		origin.accept(broadcaster, origin);
		//return broadcaster.getBroadcastTotal();
	}
	
	/**
	 * prints out the string represenation of all nodes added by this condition
	 * @pre true
	 * @post returns the string representation of all of the nodes added by this connection
	 */
	public String toString() {
		clean();	//cleans out bogus nodes from remove
		String output="";
		for(int i = 0; i<getAllNodes().size();i++){
			output+=getAllNodes().get(i);
		}
		return output;
	}
	
	/**
	 * allows for the All nodes to be set
	 * @pre allNodes should only be nodes that were added to this location
	 * or for testing
	 * @post the List of Nodes to print will change to the incoming List
	 */
	public void setAllNodes(List<Node> allNodes) {
		this.allNodes = allNodes;
	}
	
	/**
	 * Gets a list of all nodes added to this location
	 * @pre true
	 * @post returns a list of all nodes added to this location
	 */
	public List<Node> getAllNodes() {
		clean();
		return allNodes;
	}

	/**
	 * Assigns this connection root to a particular node
	 * @param myConnection - node to be assigned as the root
	 * @pre true
	 * @post the passed node is assigned as the root of the connection
	 */
	public void setMyConnection(Node myConnection) {
		this.myConnection = myConnection;
	}
	
	/**
	 * Returns the reference to the root Node of the connection
	 * @pre true
	 * @post returns the reference to the root node of the connection,
	 *  null is the connection didn't have a root
	 */
	public Node getMyConnection() {
		return myConnection;
	}
	
	/**
	 * 
	 * @return ObjectID of root node
	 */
	public static ObjectID getRootID(){
		System.out.println(myConnection);
		return myConnection.getId();
	}
	
	/**
	 * 
	 * @return size of Web
	 */
	public int getWebSize() {
		return nodeCount;
	}
		
	/**
	 * Returns a reference to a Node in the hypeerweb
	 * @pre remote is a valid and ready remote location
	 * @post returns the reference to a node in the hypeerweb
	 */
	private Node getAccessPoint(RemoteLocation remote) {
		if (remote.isHere()) {
			return getMyConnection();
		}
		Command gettingRoot = new Command(remote.getObjID(),"getRootID", new Class[0],
				new Object[0], this.getClass());
		remote.sendCommand(gettingRoot);
		ProxyNode proxy = new ProxyNode((ObjectID) remote.recieveReturn());;
		return proxy;
	}
	
	/**
	 * Returns a reference to a Node in the hypeerweb
	 * @pre id is a valid object id
	 * @post returns the reference to a node in the hypeerweb
	 */
	public Node getAccessPoint(ObjectID id) {
		RemoteLocation remote = new RemoteLocation(id);
		return getAccessPoint(remote);
	}
	
	/**
	 * Starts to listen for the incoming connections on the default port.
	 * @pre the default port is free
	 * @post applications starts to listen for the incoming connections
	 * 	on the default port
	 */
	private void listen() {
		//starting the server to listen
		listener = new Server();
		listener.start();
	//	listener.interrupt();
	}
	
	public void terminate() {
		for(int i = 0 ; allNodes.size() >0;)
		{
			System.out.println("here");
			removeNode(allNodes.get(0));
		}
		listener.interrupt();
	}
}
